*! NJC 1.4.1 20 February 2018
* NJC 1.4.0 30 June 2017
* NJC 1.3.2 11 June 2017
* NJC 1.3.1 14 May 2017
* NJC 1.3.0 31 January 2012
* NJC 1.2.5 26 January 2011 
* NJC 1.2.4 24 January 2011 
* NJC 1.2.3 21 January 2010 
* NJC 1.2.2 20 January 2010 
* NJC 1.2.1 11 April 2007
* NJC 1.2.0 30 March 2007
* NJC 1.1.1 2 August 2003 
* NJC 1.1.0 31 July 2003 
* NJC 1.0.0 27 July 2003 
program groups, sort byable(recall) 
	version 8 
	syntax varlist [if] [in] [fweight aweight/] ///
	[,  NOObs SUBVARname SUM Compress LABvar(varname) *  ///
	FILLin MISSing SHow(str) showhead(str asis) SELect(str) Order(str) ///
	FORMat(str) PERCENTvar(varlist) ge lt mid REVerse saving(str asis) ///
	colorder(numlist >0 min=1)] 

	// ignore subvarname noobs labvar() (compulsory default) 

	// what to use 
	if "`missing'" == "" marksample touse, strok 
	else marksample touse, novarlist 
	
	qui count if `touse' 
	if r(N) == 0 error 2000 
	local N = r(N) 

	// saving option 
	if "`saving'" != "" { 
		if _by() { 
			di as err "saving() option not allowed with by:" 
			exit 198 
		} 

		gettoken savefile saveopts : saving, parse(",") 

		if substr(`"`savefile'"', -4, 4) != ".dta" { 
			local ext ".dta" 
		}

		if index("`saveopts'", "replace") confirm file `"`savefile'`ext'"' 
		else confirm new file `"`savefile'`ext'"' 
	}  

	tempvar freq Freq rfreq percent Percent rpercent ///
		vfreq vpercent Vpercent rvpercent tag 

	// show option 
	if "`show'" == "none" { 
		// OK 
	} 	
	else if "`show'" != "" { 
		foreach w in `show' { 
			local OK 0 
			if lower(substr("`w'",1,1)) == "r" { 
				local w = lower("`w'") 
			} 	
			foreach s in freq Freq rfreq percent Percent rpercent vpercent Vpercent rvpercent { 
				if "`w'" == substr("`s'",1,length("`w'")) { 
					local OK 1 
					local Show "`Show'``s'' " 
					local SHOW "`SHOW'`s' " 
				}
			}
			if !`OK' { 
				di as inp "`show' " ///
				   as err "invalid argument for " /// 
				   as inp "show()"
				exit 198 
			} 	
		}
	}	
	else { 
		if `: word count `varlist'' > 1 { 
			local Show "`freq' `percent'"
			local SHOW2 "freq percent" 
		} 	
		else {
			local Show "`freq' `percent' `Percent'"
			local SHOW2 "freq percent Percent"
		}
	}

	if `"`showhead'"' != "" { 
		if `: word count `showhead'' != `: word count `Show'' { 
			di as err "showhead() does not match show()" 
			exit 198 
		}
	} 	
	
	local Cum "`Freq' `rfreq' `Percent' `rpercent' `Vpercent' `rvpercent'" 
	local nCum `: word count `: list Cum & Show'' 

	// select option 
	if "`select'" != "" {
		if real("`select'") < . { 
			capture confirm integer number `select' 
			if _rc { 
				di as inp "`select' " ///
				   as err "invalid argument for " /// 
				   as inp "select()" 
				exit 198 
			} 	
			
			if `select' == 0 exit 0 
			local Selectint 1
		} 
		else {
			tokenize "`select'", parse(" ><=") 
			local w "`1'"  
			local W : subinstr local select "`w'" ""
			
			if lower(substr("`w'",1,1)) == "r" { 
				local w = lower("`w'") 
			} 	

			local OK 0 
			foreach s in freq percent Freq Percent rfreq ///
				rpercent vpercent Vpercent rvpercent { 
				if "`w'" == substr("`s'",1,length("`w'")) { 
					local OK 1 
					local Select "``s''" 
					continue, break 
				}
			} 	
			
			// selection should specify an equality or inequality 
			qui count if 1 `W' 
			if _rc | !`OK' { 
				di as inp "`select' " ///
				   as err "invalid argument for " /// 
				   as inp "select()"
				exit 198 
			} 	

			local Selectint 0 
		} 	
	}

	// order option 
	if "`order'" != "" { 
		if `: word count `order'' > 1 { 
			di as err "invalid " as inp "order()" as err "option"
			exit 198 
		} 
		
		local orderlist "h hi hig high l lo low" 
		if !`: list order in orderlist' { 
			di as inp "`order' " ///
			   as err "invalid argument for " /// 
			   as inp "order()"
			exit 198    
		}
		local order = substr("`order'",1,1) 
	}

	// format option 
	if "`format'" != "" { 
		capture di `format' 1 
		if _rc { 
			di as err "invalid format " ///
			   as inp "`format' " /// 
			   as err "in " as inp "format()" 
			exit 120 
		} 
	} 	
	else local format "%6.2f" 	

	preserve 
	
	// fillin option 
	if "`fillin'" != "" {
		if `: word count `varlist'' == 1 { 
			// -fillin- makes no sense, but ignore it anyway
			local indata 1
		} 	
		else { 	
			capture confirm new variable _fillin
			if _rc { 
				di as err ///
				"fillin option invalid: _fillin already exists"
				exit 198 
			}
			
			qui keep if `touse' 
			myfillin `varlist' 
			qui replace `touse' = 1 if `touse' == . 
			local indata  "!_fillin"
		} 	
	} 	
	else local indata 1 

	if "`percentvar'" != "" local bylist "`percentvar'" 
	
	// no weights specified? 
	if "`exp'" == "" local exp 1  

	// some calculations done even if user doesn't want to see results
	// there is some inefficiency in this; on the other hand, there 
	// are reasonable requests such -select(f == 1) show(none)- 
	// that should certainly be allowed 
	
	qui {
		local Varlist : subinstr local varlist " " ",", all 
		bysort `touse' `varlist' : gen byte `tag' = _n == 1 & `touse' 

		by `touse' `varlist' : gen double `freq' = ///
			sum(`exp' * `touse' * `indata')  
		by `touse' `varlist' : replace `freq' = `freq'[_N]

		by `touse' `varlist' : gen double `vfreq' = ///
			sum(`exp' * `touse' * `indata' * !missing(`Varlist'))  
		by `touse' `varlist' : replace `vfreq' = `vfreq'[_N]

		if "`weight'" == "aweight" { 
			su `exp' if `touse' & `indata', meanonly 
			replace `freq' = `freq' * `N' / r(sum)   
			su `exp' if `touse' & `indata' & !missing(`Varlist'), meanonly 
			replace `vfreq' = `vfreq' * `N' / r(sum)   
		}
		
		by `touse' `bylist' : gen `percent' = sum(`freq' * `tag') 
		by `touse' `bylist' : replace `percent' = `percent'[_N] 
		replace `percent' = cond(`tag', 100 * `freq' / `percent', 0) 

		by `touse' `bylist' : gen `vpercent' = sum(`vfreq' * `tag') 
		by `touse' `bylist' : replace `vpercent' = `vpercent'[_N] 
		replace `vpercent' = cond(`tag', 100 * `vfreq' / `vpercent', 0) 
	
		if "`order'" == "h" {
			tempvar negfreq 
			gen double `negfreq' = - `freq' 
			sort `touse' `bylist' `negfreq' `varlist' 
		} 
		else if "`order'" == "l" { 
			sort `touse' `bylist' `freq' `varlist' 
		} 	
			
		by `touse' `bylist' : gen `Freq' = sum(`freq' * `tag')
		by `touse' `bylist' : gen `rfreq' = `Freq'[_N] - `Freq' 
		by `touse' `bylist' : gen `Percent' = sum(`percent')
		by `touse' `bylist' : gen `Vpercent' = sum(`vpercent')
		gen `rpercent' = 100 - `Percent'
		gen `rvpercent' = 100 - `Vpercent'

		if "`ge'" != "" { 
			replace `rfreq' = `rfreq' + `freq' 
			replace `rpercent' = `rpercent' + `percent'
			replace `rvpercent' = `rvpercent' + `vpercent' 
		}
		if "`lt'" != "" { 
			replace `Freq' = `Freq' - `freq' 
			replace `Percent' = `Percent' - `percent'
			replace `Vpercent' = `Vpercent' - `vpercent' 
		}
		if "`mid'" != "" { 
			replace `Percent' = `Percent' - 0.5 * `percent'
			replace `rpercent' = 100 - `Percent' 
		}

		foreach v in `vpercent' `Vpercent' `rvpercent' { 
			replace `v' = . if missing(`Varlist') 
		}

		if `"`showhead'"' != "" { 
			tokenize `"`showhead'"'  
			
			local i = 1 
			foreach v of var `Show' { 
				label var `v' `"``i''"'
				char `v'[varname] `"``i++''"' 
			}
		} 	
		else { 	
			if "`compress'" == "" { 
				char `freq'[varname]      "Freq."
				char `percent'[varname]   "Percent"
				char `vpercent'[varname]  "% Valid"
			} 
			else { 
				char `freq'[varname]      "#"
				char `percent'[varname]   "%"
				char `vpercent'[varname]  "% V."
			}	

			if "`mid'" == "" { 
				local less = cond("`lt'" != "", " <", " <=") 
				local more = cond("`ge'" != "", " >=", " >") 
			}
			
			char `Freq'[varname]      "#`less'"
			char `Percent'[varname]   "%`less'"
			char `Vpercent'[varname]  "% V.`less'"
				
			char `rfreq'[varname]     "#`more'"
			char `rpercent'[varname]  "%`more'"
			char `rvpercent'[varname] "%`more'"

			if "`mid'" != "" { 
				local less "mid" 
				local more "mid" 
			}

			label var `freq' "frequency" 
			label var `Freq' "cumulative frequency (`less')" 
			label var `rfreq' "cumulative frequency (`more')" 
			label var `percent' "percent" 
			label var `vpercent' "valid percent" 
			label var `Percent' "cumulative percent (`less')" 
			label var `rpercent' "cumulative percent (`more')" 
			label var `Vpercent' "cumulative valid percent (`less')" 
			label var `rvpercent' "cumulative valid percent (`more')" 
		} 	

		format `percent' `Percent' `rpercent' ///
			`vpercent' `Vpercent' `rvpercent' `format' 

		if "`select'" != "" {
			if `Selectint' {
				replace `tag' = sum(`tag') * `tag' 
				
				if `select' > 0 { 
					replace `tag' = ///
					`tag' & `tag' <= `select' 
				} 		
				else { 
					su `tag', meanonly 
					replace `tag' = ///
					`tag' & `tag' > (r(max) + `select') 
				} 	
			} 	
			else replace `tag' = `tag' & `Select' `W' 
		} 	
	}

	// what to sum 
	if "`sum'" != "" { 
		if index("`Show'", "`freq'") local Sum "`freq'" 
		if index("`Show'", "`percent'") local Sum "`Sum' `percent'" 
		if index("`Show'", "`vpercent'") local Sum "`Sum' `vpercent'" 
		if "`Sum'" != "" local sum "sum(`Sum')" 
		else local sum 
	} 	

	if "`reverse'" != "" { 
		tempvar obs
		gen long `obs' = -_n  
		sort `obs' 
	}

	local listvars `varlist' `Show' 

	if "`colorder'" != "" {
		tokenize `listvars'

		foreach r of local colorder { 
			local newlist `newlist' ``r'' 
		} 

		local rest : list listvars - newlist 
		local listvars `newlist' `rest' 
	} 

	list `listvars' if `tag', `compress' ///
	labvar(`: word 1 of `varlist'') noobs subvarname `sum' `options' 

	quietly if `"`saving'"' != "" { 
		keep if `tag' 
		keep `listvars' 
		
		foreach v in `SHOW' `SHOW2' { 
			capture clonevar _`v' = ``v'' 
			
			if _rc == 0 { 
				local tokeep `tokeep' _`v' 
			}
			else { 
				di as err "implied new variable name _`v' was problematic; rename variable first" 
				exit 498 
			} 
		} 

		compress `tokeep' 
		keep `varlist' `tokeep' 
		noisily di 
		noisily save `"`savefile'"' `saveopts' 
	} 
end 

* avoid problems if _merge already exists 
* -fillin- version 2.1.2  19dec1998
program myfillin
	version 8
	syntax varlist(min=2)
	tokenize `varlist'
	tempfile FILLIN0 FILLIN1
	tempvar merge  
	preserve
	quietly {
		keep `varlist' 
		save `"`FILLIN0'"', replace
		keep `1'
		bysort `1':  keep if _n == _N
		save `"`FILLIN1'"', replace 
		mac shift 
		while "`1'" != "" { 
			use `"`FILLIN0'"', clear
			keep `1'
			bysort `1': keep if _n == _N
			cross using `"`FILLIN1'"'
			save `"`FILLIN1'"', replace 
			mac shift
		}
		erase `"`FILLIN0'"'		/* to save disk space only */
		sort `varlist'
		save `"`FILLIN1'"', replace 
		restore, preserve
		sort `varlist' 
		merge `varlist' using `"`FILLIN1'"', _merge(`merge') 
		noisily assert `merge' != 1
		gen byte _fillin = `merge' == 2
		drop `merge' 
		sort `varlist'
		restore, not
	}
end


